import fs from 'fs';
import MysqlConfig from "../config/MysqlConfig";
import ReadisConfig from "../config/ReadisConfig";
import Cache from "../cache/Cache";
import MetaData from "../expression/MetaData";
import Logc from "@baige/logc";
import * as process from "process";
import {Pool} from 'mysql2/promise';
import mysql2 from "mysql2"
import DatabaseInfo from "../model/DatabaseInfo";
import UnchangedExpression from '../expression/impl/UnchangedExpression';

const logc = Logc.getLogc("@baige/mysql", {
    prefix: "mysql"
})

export = class MysqlCore {
    /** mysql的配置 */
    public mysqlConfig: MysqlConfig;
    /** sql缓存 */
    public cache: Cache;
    /** 连接池 */
    public pool: Pool;

    /** 构造器 */
    public constructor(mysqlConfig?: MysqlConfig, redistConfig?: ReadisConfig) {
        if (mysqlConfig != undefined) {
            this.mysqlConfig = new MysqlConfig(mysqlConfig);
            this.cache = Cache.createCache(redistConfig);
        } else {
            // 尝试从项目根目录获取配置
            if (fs.existsSync(process.cwd() + "/mysql.config.json")) {
                let data: any = JSON.parse(fs.readFileSync(process.cwd() + "/mysql.config.json", 'utf8'));
                this.mysqlConfig = new MysqlConfig(data.mysql || data);
                this.cache = Cache.createCache(data.readis || this.mysqlConfig.readis);
            } else {
                throw new Error("mysql config is null");
            }
        }

        // 判断参数
        if (!this.mysqlConfig.host) throw new Error("缺少host")
        if (!this.mysqlConfig.username) throw new Error("缺少user")
        if (!this.mysqlConfig.database) throw new Error("缺少database")

        // 创建连接池
        this.pool = mysql2.createPool({
            host: this.mysqlConfig.host,
            port: Number(this.mysqlConfig.port) || 3306,
            user: this.mysqlConfig.username,
            password: this.mysqlConfig.password,
            database: this.mysqlConfig.database,
            waitForConnections: true,
            connectionLimit: 10,
            queueLimit: 0
        }).promise();

        let select = this.select;
        let c = this.connection();
        this.select = async (option: MetaData) => {
            await c;
            this.select = select;
            await DatabaseInfo.getAll_Table(this)
            return await this.select(option);
        }
    }

    /** 执行连接 */
    public async connection() {
        while (true) {
            try {
                // 创建一次连接然后关闭，用来测试数据库是否连接正常
                (await this.pool.getConnection()).release();
                //开始初始化，获取全部表结构数据
                await this.cache.init();
                break;
            } catch (e: any) {
                logc.error("mysql连接失败", e.message);
                // 10秒之后继续尝试连接
                setTimeout(() => this.connection(), 10 * 1000);
            }
        }
        return this;
    }


    /** 执行查询方法 */
    public async nativeSelect<T = Array<Record<string, any>> | Record<string, any>>(sql: string, parameter: [] = []): Promise<T> {
        let m = new MetaData(this, "underline", "smallHump")
        m.parameter = parameter;
        m.table = new UnchangedExpression(sql, m);
        m.add(m.table);
        return this.select(m)
    };

    public async select<T = Array<Record<string, any>> | Record<string, any>>(option: MetaData): Promise<T> {
        let sql = option.toSql();
        if (option.isSubquery) {
            return sql as unknown as T;
        }
        //查询缓存|清除缓存
        switch (option.type) {
            case "select": {
                // 缓存如果存在，直接返回缓存
                if (await this.cache.has(sql + option.parameter)) {
                    return this.cache.get(sql + option.parameter);
                }
                break;
            }
            case "insert"://清除缓存
            case "update"://清除缓存
            case "delete": {
                await this.cache.delete(option.table.toLocaSql());
            }
        }
        //调用生命周期函数
        console.log("sql: " + option.toSql(true));
        let data = await this.pool.query(sql, option.parameter);
        return data[0] as unknown as T;
    }
}